package com.qf;

import com.qf.captcha.CaptchaCodeManager;
import com.qf.pojo.DtsAdmin;
import com.qf.service.PermissionService;
import com.qf.service.RoleService;
import com.qf.util.Base64;
import com.qf.util.UUID;
import com.qf.util.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.*;

import java.io.ByteArrayOutputStream;
import java.util.*;

/**
 * 权限模块controller
 * @author
 * @version V1.0
 * @Project java2203-mkr
 * @Package com.qf.controller
 * @Description:
 * @Date 2022/6/16 14:14
 */
//@RestController:表示这是一个controller类，交给spring容器管理
//并且，这个类中所有方法的返回值都会自动转换成json格式返回
@RestController
@RequestMapping("/auth")
//解决前后端跨域访问问题
@CrossOrigin("*")
@Api(value = "权限模块：登录、注销、验证码")
public class AuthController {
    //打印日志对象
    private  static final Logger logger = LoggerFactory.getLogger(AuthController.class);

    @Autowired
    private RoleService roleService;

    @Autowired
    private PermissionService permissionService;

    @Autowired
    private ApplicationContext context;

    private HashMap<String,String> systemPermMap = null;

    /**
     * 获取验证码
     * @return
     */
    @GetMapping("/captchaImage")
    @ApiOperation(value = "生成页面登录验证码")
    public Object findCode()throws Exception{
        //1、生成随机字符串作为验证码
        String codeStr = VerifyCodeUtils.generateVerifyCode(4);

        //2、生成验证码唯一标识
        String uuid = UUID.randomUUID().toString(true);

        //3、验证码存储到内存中
        boolean flag = CaptchaCodeManager.addToCache(uuid, codeStr, 10);

        //4、判断验证码是否能够存储成功
        if (!flag){
            logger.error("生成验证码错误:{}", AdminResponseCode.AUTH_CAPTCHA_FREQUENCY);
            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_FREQUENCY);
        }

        //5、生成验证码图片
        int w = 100;
        int h = 40;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        VerifyCodeUtils.outputImage(w,h,outputStream,codeStr);

        //6、将图片唯一id和图片内容返回
        Map<String,Object> data = new HashMap<>();
        data.put("img", Base64.encode(outputStream.toByteArray()));
        data.put( "uuid", uuid);

        //返回封装的数据
        return ResponseUtil.ok(data);
    }

    /**
     * 登录
     * @param body
     * @return
     */
    @RequestMapping("/login")
    @ApiOperation(value = "用户登录")
    public Object login(@RequestBody String body){
        //1、从传入的json字符串中提取参数数据
        String code = JacksonUtil.parseString(body,"code");
        String username = JacksonUtil.parseString(body,"username");
        String password = JacksonUtil.parseString(body,"password");
        String uuid = JacksonUtil.parseString(body,"uuid");
        //2、判断参数是否为空。如果为空返回信息
        if (StringUtils.isEmpty(code)||StringUtils.isEmpty(username)
            ||StringUtils.isEmpty(password) ||StringUtils.isEmpty(uuid)){

        }

        String cacheCode = CaptchaCodeManager.getCachedCaptcha(uuid);
        //判断验证码是否超时
        if (cacheCode == null) {
            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_EXPIRED);
        }
        if (!code.equalsIgnoreCase(cacheCode)) {
            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_ERROR);
        }

        //4. 创建shiro的subject对象
        Subject subject = SecurityUtils.getSubject();

        //5. 创建shiro的用户名密码对象, 封装用户页面中输入的用户名, 密码
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);

        //6. 调用登录方法, 传入用户名密码对象, 就会触发shiro的自定义realm进行校验
        try {
            subject.login(usernamePasswordToken);
        } catch (UnknownAccountException e) {
            return AdminResponseUtil.fail(AdminResponseCode.ADMIN_INVALID_ACCOUNT_OR_PASSWORD);
        }catch (AuthenticationException e) {
            return AdminResponseUtil.fail(AdminResponseCode.ADMIN_INVALID_AUTH);
        }

        //7. 返回登录成功信息
        return ResponseUtil.ok(subject.getSession().getId());
    }

    /**
     * 获取用户登录后的详细信息、用户名、角色、权限
     * @return
     */
    @RequestMapping("/info")
//    @ApiOperation(value = "获取用户详细信息、用户名、角色、权限")
    public Object info(){

        //1、从shiro框架中获取当前用户对象
        Subject subject = SecurityUtils.getSubject();
        //2、获取当前登录用户的用户名、用户头像
        DtsAdmin admin = (DtsAdmin)subject.getPrincipal();
        Map<String,Object> data = new HashMap<>();
        data.put("name", admin.getUsername());
        data.put("avatar", admin.getAvatar());
        //3、获取当前用户中的角色id数组
        Integer[] roleIds = admin.getRoleIds();
        //4. 根据角色id数组到数据库中查询角色名称集合
        Set<String> roleSet = roleService.queryById(roleIds);
        data.put("roles", roleSet);

        //5. 根据角色id数组到数据库中查询权限字符串集合
        Set<String> permSet = permissionService.queryPermissonByRoleIds(roleIds);

        //6. 将权限字符串集合, 转换成前端需要的权限字符串数组
        data.put("perms", toAPI(permSet));

        //7. 返回
        return ResponseUtil.ok(data);
    }

    /**
     * 注销退出
     * @return
     */
    @RequestMapping("/logout")
    public Object logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return ResponseUtil.ok();
    }

    /**
     * 将权限字符串集合转换成api形式的权限集合
     * @param permSet
     * @return
     */
    private Collection<String> toAPI(Set<String> permSet){

        //封装所有的controller方法, 这些方法应该具有什么样的权限字符串才能够访问
        //systemPermMap中的数据例如: key是admin:brand:list value: POST /admin/brand/list
        if (systemPermMap == null || systemPermMap.size() == 0) {
            systemPermMap = new HashMap<>();
            //设置我们controller所在的包名
            String basicPackage = "com.qf";
            //通过反射机制, 获取指定包中的controller中带自定义权限注解的方法
            List<Permission> systemPersionList = PermissionUtil.listPermission(context, basicPackage);
            //遍历
            for (Permission permission : systemPersionList) {
                //权限字符串数组
                String[] perm = permission.getRequiresPermissions().value();
                //对应的能够访问的url路径
                String api = permission.getApi();
                //key是权限字符串, value是能够访问的url路径
                systemPermMap.put(perm[0], api);
            }
        }

        //封装用来存放当前用户能够访问的url地址
        //例如:  POST /admin/admin/delete
        Collection<String> apis = new HashSet<>();

        //遍历传入的这个登录用户的权限字符串集合
        for (String perm : permSet) {
            //根据权限字符串, 获取对应的url访问路径
            String api = systemPermMap.get(perm);
            apis.add(api);

            if (perm.equals("*")) {
                apis.clear();
                apis.add("*");
                return apis;
            }
        }
        return apis;
    }

}
